/*  dreic -- a compiler for Drei
 *
 *  $Id: Generator.scala 3383 2007-01-19 11:43:51Z mihaylov $
 */

package dreic

class Generator(analyzer: Analyzer) {
    import RISC._

    val code = new Code()
    val reg = new RegStack()
    
    def gen(tree: Tree): Unit =  {
        tree match {
            case Program(classes, statement) => {
                code.emit(BEQ, code.getLabel("init"))
                classes.map(gen)
                gen(statement)
                code.getLabel("init").setAnchor
                
                // initialise stack pointer
                code.emit(SYSCALL, SP, 0, SYS_GET_TOTAL_MEM_SIZE)
                // initialise garbage collector:
                // the heap starts at address init and its size is 2/3 * (total_mem_size - init) words
                val heapStart = code.pc
                emitLoadConstant(1, heapStart)
                code.emit(SUB, 2, SP, 1)
                code.emit(DIVIU, 2, 2, 3 * WORD_SIZE)
                code.emit(LSHI, 2, 2, 1) // r2 now contains the size of the heap
                emitLoadConstant(3, SP << 27)
                code.emit(ADD, 2, 2, 3)
                code.emit(SYSCALL, 1, 2, SYS_GC_INIT)
                code.emit(BEQ, code.getLabel("main"))   
            }
            case ClassDef(name, name2, membres) =>
            case FieldDecl(name, field_type) =>
            case MethodDef(name, args, ret_type, body) =>
            case While(cond, stats) =>
            	val startLabel = code.getLabel
							startLabel.setAnchor
							val endLabel = code.getLabel
							genCond(cond, endLabel, false)
							stats.map(gen)
							code.emit(BEQ, 0, startLabel)
							endLabel.setAnchor
            case If(cond, iftrue, iffalse) =>
            	val elseLabel = code.getLabel
							genCond(cond, elseLabel, false)
							gen(iftrue)
							val afterLabel = code.getLabel
							code.emit(BEQ, ZERO, afterLabel)
							elseLabel.setAnchor
							gen(iffalse)
							afterLabel.setAnchor
            case Var(varname, vartype, init) => {
                vartype match {
                    case i: IntType => {
                    }
                    case _ => {
                    	genLoad(init)
                    }
                }
            }
            case Set(name, expr) =>
            case Do(expr) => genLoad(expr)
            case PrintInt(expr) => {
            		genLoad(expr)
                /*val dest: Int = reg.fresh
                code.emit(ORI, dest, 0, expr.value)*/
                code.emit(SYSCALL, reg.top, 0, SYS_IO_WR_INT)
                reg.drop
            }
            case PrintChar(expr) => {
            		genLoad(expr)
                /*val dest: Int = reg.fresh
                code.emit(ORI, dest, 0, expr.value)*/
                code.emit(SYSCALL, reg.top, 0, SYS_IO_WR_INT)
                reg.drop
            }
            case Compound(stats) => {
                stats.map(gen)
            }
            
            // forwarder les expressions
            case Ident(name) => genLoad(tree)
            case New(name, exprs) => genLoad(tree)
            case Select(expr, name) => genLoad(tree)
            case Call(expr, name, exprs) => genLoad(tree)
            case IntLit(value) => genLoad(tree)
            case NullLit() => genLoad(tree)
            case Unop(op, expr) => genLoad(tree)
            case Binop(op, left, right) => genLoad(tree)
            case ReadInt() => genLoad(tree)
            case ReadChar() => genLoad(tree)
            case Block(stats, return_value) => genLoad(tree)
            
            // forwarder les conditions
            case Not() =>
            case Neg() =>
            case Add() =>
            case Sub() =>
            case Mul() =>
            case Div() =>
            case Mod() =>
            case Eq() =>
            case Ne() =>
            case Lt() =>
            case Le() =>
            case Gt() =>
            case Ge() =>
            case And() =>
            case Or() =>
        }
        null
    }
    
    def genLoad(tree: Tree) : Unit = {
        tree match {
        		case Var(name) => {
        			code.incFrameSize(1)
        			
        		}
            case Ident(name) =>
            case New(name, exprs) => {
            	val pointer = reg.fresh
            	val dest = reg.fresh
            	code.emit(ADDI, dest, 0, exprs.length)
            	code.emit(SYSCALL, pointer, dest, SYS_GC_ALLOC)
            	reg.drop
            	var i = 0;
            	exprs.forall(e =>
            		{
	            		genLoad(e)
	            		code.emit(STW, reg.top, pointer, -4*i)
	            		reg.drop
	            		i = i + 1
	            		true
	            	}
							)
            }
            case Select(expr, name) => {
            	genLoad(expr)
            	val dest = reg.fresh
            	name.sym match{
            		case f : FieldSymbol => code.emit(LDW, reg.top, reg.second, f.offset)
            	}
            }
            case Call(expr, name, exprs) => {
            	
            }
            case IntLit(value) => {
                val dest: Int = reg.fresh
                code.emit(ADDI, dest, 0, value)
            }
            
            case NullLit() =>
            	
            case Unop(Neg(), expr) => code.emit(MULI, reg.top, reg.top, -1)
            	
            case Binop(op, left, right) => {
                genLoad(left)
                genLoad(right)
                op match {
                    case b: Add => {
                        code.emit(ADD, reg.second, reg.second, reg.top)
                        reg.drop
                    }
                    case b: Sub => {
                        code.emit(SUB, reg.second, reg.second, reg.top)
                        reg.drop
                    }
                    case b: Mul => {
                        code.emit(MUL, reg.second, reg.second, reg.top)
                        reg.drop
                    }
                    case b: Div => {
                        code.emit(DIV, reg.second, reg.second, reg.top)
                        reg.drop
                    }
                    case b: Mod => {
                        code.emit(MOD, reg.second, reg.second, reg.top)
                        reg.drop
                    }
                }
            }
            case ReadInt() => {
                val dest: Int = reg.fresh
                code.emit(SYSCALL, dest, 0, SYS_IO_RD_CHR)
            }
            case ReadChar() => {
                val dest: Int = reg.fresh
                code.emit(SYSCALL, dest, 0, SYS_IO_RD_INT)
            }
        }
    }
    
    def genCond(tree: Tree, targetLabel: code.Label, when: Boolean) : Unit = {
        tree match {
        		case IntLit(value) => {
        			value match{
        				case 0 => code.emit(if (when) BEQ else BNE, 0, targetLabel)
								case 1 => code.emit(if (when) BEQ else BNE, 0, targetLabel)
        			}
        		}
        		
        		case Unop(Not(), expr) => Console.println("not") ; genCond(expr, targetLabel, !when)
        		
            case Binop(op, left, right) => {
                genLoad(left)
                genLoad(right)
                code.emit(CMP, reg.second, reg.top, reg.top)
                reg.drop
                op match {
                    case b: Eq => code.emit(if (when) BEQ else BNE, reg.top, targetLabel)
                    case b: Ne => code.emit(if (when) BNE else BEQ, reg.top, targetLabel)
                    case b: Lt => code.emit(if (when) BLT else BGE, reg.top, targetLabel)
                    case b: Le => code.emit(if (when) BLE else BGT, reg.top, targetLabel)
                    case b: Gt => code.emit(if (when) BGT else BLE, reg.top, targetLabel)
                    case b: Ge => code.emit(if (when) BGE else BLT, reg.top, targetLabel)
                    case b: Or =>
											if (when) {
												genCond(left, targetLabel, true)
												genCond(right, targetLabel, true)
											} else {
												val afterLabel = code.getLabel
												genCond(left, afterLabel, true)
												genCond(right, targetLabel, false)
												afterLabel.setAnchor
											}
                    case b: And =>
											if (when) {
												val afterLabel = code.getLabel
												genCond(left, afterLabel, false)
												genCond(right, targetLabel, true)
												afterLabel.setAnchor
											} else {
												genCond(left, targetLabel, false)
												genCond(right, targetLabel, false)
											}
                }
            }
        }
    }
    
    def emitLoadConstant(reg: Int, const: Int) {
        code.emit(ADDIU, reg, 0, const >> 32)
        code.emit(LSH, reg, reg, 32)
        code.emit(ADDIU, reg, 0, const & 0xFFFFFFFF)
    }
}
